package com.tsd.anon;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.WxMaUserService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import com.alibaba.fastjson.JSON;
import com.tsd.common.controller.BaseController;
import com.tsd.core.constants.JwtConstants;
import com.tsd.core.datasource.DataSourceContextHolder;
import com.tsd.core.jwt.AccessToken;
import com.tsd.core.jwt.JwtUtils;
import com.tsd.core.utils.ExceptionUtil;
import com.tsd.core.utils.HlpUtils;
import com.tsd.core.vo.ErrorCode;
import com.tsd.core.vo.HlpException;
import com.tsd.core.vo.HttpResult;
import com.tsd.jwt.JwtHelper;
import com.tsd.system.dao.SysRegUserBindMapper;
import com.tsd.system.entity.SysRegUserExt;
import com.tsd.system.entity.SysUserExt;
import com.tsd.system.service.SysRegUserBindService;
import com.tsd.system.service.SysRegUserService;
import com.tsd.system.service.SysUserService;
import com.tsd.system.service.WxSettingService;
import com.tsd.system.vo.MiniappSetting;
import com.tsd.system.vo.MpSetting;
import com.tsd.system.vo.OpenSetting;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.service.WxOAuth2Service;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage;
import me.chanjar.weixin.open.api.impl.WxOpenOAuth2ServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLDecoder;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName: AnonWeixinController
 * @Description: 匿名微信的控制器
 * 未登录时需要获取数据的方法都写这里
 * @Author: Hillpool
 * @Date: 2022/2/14 14:01
 * @Version: 1.0
 */
@Controller
@Scope("prototype")
@RequestMapping("/anon")
@Api(tags = "匿名微信", description = "匿名微信的控制器")
public class AnonWeixinController extends BaseController {
    private static final Logger LOGGER = LoggerFactory.getLogger(AnonWeixinController.class);

    private static final WxMpService WX_MP_SERVICE;
    private static final WxMaService WX_MA_SERVICE;

    @Resource
    private WxSettingService wxSettingService;
    @Resource
    private SysRegUserService sysRegUserService;
    @Resource
    private SysRegUserBindService sysRegUserBindService;
    //    @Resource
//    private SysClientService sysClientService;
//    @Resource
//    private SysClientOperatorService sysClientOperatorService;
    @Resource
    private SysRegUserBindMapper sysRegUserBindMapper;
    @Resource
    private SysUserService sysUserService;

    static {
        WX_MP_SERVICE = new WxMpServiceImpl();
        WX_MA_SERVICE = new WxMaServiceImpl();
    }

    @ApiOperation(value = "获取微信登录配置", notes = "获取微信登录配置")
    @ResponseBody
    @RequestMapping(value = "/getWxOpenSetting.action", method = RequestMethod.POST)
    public HttpResult<Object> getWxOpenSetting() {
        HttpResult<Object> hr = new HttpResult<>();
        try {
            super.setAccountSetFromParams();
            OpenSetting setting = wxSettingService.getWxOpenSetting();
            Map<String, Object> result = new HashMap<>();
            result.put("appid", setting.getWx_open_appid());
            result.put("redirect_uri", setting.getWx_open_callback_url());
            result.put("selfRedirect", HlpUtils.isEmpty(setting.getWx_open_redirect_url()) ? "true" : "false");
            hr.setData(result);
            hr.setCode(ErrorCode.CODE_SUCCESS);
        } catch (Exception e) {
            super.procException(hr, e);
        }
        return hr;
    }

    @ApiOperation(value = "微信扫码登录成功回调")
    @RequestMapping(value = "/wxLoginCallBack/{accountSetSid}", method = RequestMethod.GET)
    public void wxLoginCallBack(@PathVariable("accountSetSid") String accountSetSid, HttpServletResponse response) throws Exception {
        DataSourceContextHolder.setDataSourceType(accountSetSid);
        OpenSetting setting = wxSettingService.getWxOpenSetting();
        String code = request.getParameter("code");
        String state = request.getParameter("state");
        if (!HlpUtils.isEmpty(code)) {
            try {
                WxOpenOAuth2ServiceImpl wxOAuth2Service = new WxOpenOAuth2ServiceImpl(setting.getWx_open_appid(), setting.getWx_open_app_secret());
                wxOAuth2Service.setWxOpenConfigStorage(new WxOpenInMemoryConfigStorage());
                // 1、获取access token
                WxOAuth2AccessToken token = wxOAuth2Service.getAccessToken(code);
                LOGGER.info("获取access_token返回：{}", JSON.toJSONString(token));
                if (!HlpUtils.isEmpty(state) && state.startsWith("bind_")) {
                    // 绑定微信
                    this.bindWx(state, token, response);
                } else {
                    // 微信扫码登录
                    this.wxScanLogin(setting, state, token, wxOAuth2Service, response);
                }
            } catch (WxErrorException e) {
                this.writerScript(response, "alert('授权失败：" + e.getMessage() + "');");
                LOGGER.error(e.getMessage());
            } catch (Exception e) {
                this.writerScript(response, "alert('" + e.getMessage() + "');");
                LOGGER.error(ExceptionUtil.getStackTraceFromException(e));
            }
        } else {
            this.writerScript(response, "alert('取消授权');");
        }
    }

    /**
     * 绑定微信
     *
     * @param state
     * @param token
     * @throws Exception
     */
    private void bindWx(String state, WxOAuth2AccessToken token, HttpServletResponse response) throws Exception {
        String data = state.replace("bind_", "");
        String[] strings = data.split("_");
        if (strings.length != 2) {
            return;
        }
        String origin = URLDecoder.decode(strings[1], "UTF-8");
        SysRegUserExt userExt = sysRegUserService.queryBySid(strings[0]);
        if (userExt == null) {
            throw new HlpException("注册用户不存在");
        }
        if (!HlpUtils.isEmpty(userExt.getOpen_id())) {
            throw new HlpException("该用户已绑定微信");
        }
        // 判断当前微信是否已绑定用户
        SysRegUserExt existUser = null;
        if (!HlpUtils.isEmpty(token.getUnionId())) {
            existUser = sysRegUserService.queryValidByUnionId(token.getUnionId());
        }
        if (existUser == null && !HlpUtils.isEmpty(token.getOpenId())) {
            existUser = sysRegUserService.queryValidByOpenId(token.getOpenId());
        }
        if (existUser != null) {
            throw new HlpException("该微信已绑定用户：" + (!HlpUtils.isEmpty(existUser.getLogin_id()) ? existUser.getLogin_id() : existUser.getOpen_id()));
        }
        userExt.setOpen_id(token.getOpenId());
        if (HlpUtils.isEmpty(userExt.getAuth_src())) {
            userExt.setAuth_src(SysRegUserExt.AUTH_SRC_OPEN);
        }
        sysRegUserService.saveSysRegUser(userExt, null);
        this.writerScript(response, "window.parent.postMessage('" + userExt.getOpen_id() + "', '" + origin + "')");
    }

    /**
     * 微信扫码登录
     *
     * @param token
     * @param wxOAuth2Service
     * @throws Exception
     */
    private void wxScanLogin(OpenSetting setting, String state, WxOAuth2AccessToken token,
                             WxOpenOAuth2ServiceImpl wxOAuth2Service, HttpServletResponse response) throws Exception {
        SysRegUserExt userExt = sysRegUserService.queryValidByUnionIdOrOpenId(token.getUnionId(), token.getOpenId());
        if (userExt == null) {
            WxOAuth2UserInfo userInfo = wxOAuth2Service.getUserInfo(token, null);
            LOGGER.info("获取用户个人信息返回：{}", JSON.toJSONString(userInfo));
            userExt = this.convert(userInfo);
        } else {
            if (!HlpUtils.isEmpty(token.getOpenId())) {
                userExt.setOpen_id(token.getOpenId());
            }
            if (!HlpUtils.isEmpty(token.getUnionId())) {
                userExt.setUnion_id(token.getUnionId());
            }
            userExt.setLast_logined_date(new Date());
            userExt.setLogin_count(userExt.getLogin_count() == null ? 1 : userExt.getLogin_count() + 1);
        }
        if (HlpUtils.isEmpty(userExt.getAuth_src())) {
            userExt.setAuth_src(SysRegUserExt.AUTH_SRC_OPEN);
        }
        sysRegUserService.saveSysRegUser(userExt, null);

        SysUserExt opUser = new SysUserExt(userExt);
        String jwtToken = JwtHelper.createJWT(JwtConstants.TYPE_REG_USER, opUser);
        if (!HlpUtils.isEmpty(setting.getWx_open_redirect_url())) {
            // 方式1：跳转界面
            String s = setting.getWx_open_redirect_url();
            String redirectTemplate = state + (s.startsWith("/") ? "" : "/") + s + "?token=%s";
            String redirectURL = String.format(redirectTemplate, jwtToken);
            response.sendRedirect(redirectURL);
        } else {
            // 方式2：直接返回script，
            this.writerScript(response, "window.parent.postMessage('" + jwtToken + "', '" + state + "')");
        }
    }

    private void writerScript(HttpServletResponse response, String script) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.println("<script type='text/javascript'>");
        writer.println(script);
        writer.println("</script>");
        writer.flush();
        writer.close();
    }

    private SysRegUserExt convert(WxOAuth2UserInfo userInfo) {
        SysRegUserExt userExt = new SysRegUserExt();
        userExt.setOpen_id(userInfo.getOpenid());
        userExt.setUnion_id(userInfo.getUnionId());
        userExt.setName(userInfo.getNickname());
        userExt.setSex(userInfo.getSex());
        userExt.setCountry(userInfo.getCountry());
        userExt.setProvince(userInfo.getProvince());
        userExt.setCity(userInfo.getCity());
        userExt.setHead_pic(userInfo.getHeadImgUrl());
        userExt.setUser_type(SysRegUserExt.USER_TYPE_PUTONG);
        userExt.setBind_date(new Date());
        userExt.setLast_logined_date(new Date());
        userExt.setLogin_count(1);
        return userExt;
    }

    @ApiOperation(value = "获取微信公众号OpenId")
    @ResponseBody
    @RequestMapping(value = "/getOpenId.action", method = RequestMethod.POST)
    public HttpResult<Object> getOpenId() {
        HttpResult<Object> hr = new HttpResult<>();
        try {
            super.setAccountSetFromParams();
            Map<String, Object> result = new HashMap<>();
            String dataSource = DataSourceContextHolder.getDataSourceType();
            Map<String, Object> map = super.getParams(request);
            String accessCode = HlpUtils.getString(map, "access_code");
            String userSid = HlpUtils.getString(map, "user_sid");
            MpSetting setting = wxSettingService.getWxMpSetting();
            result.put("app_id", setting.getWechat_app_id());
            if (HlpUtils.isEmpty(accessCode)) {
                return HttpResult.success(result);
            }
            // 1、初始化
            WX_MP_SERVICE.addConfigStorage(dataSource, setting.initWxMpConfig());
            // 2、获取 access_token
            WxOAuth2Service auth2Service = WX_MP_SERVICE.getOAuth2Service();
            WxOAuth2AccessToken token = auth2Service.getAccessToken(accessCode);
            LOGGER.info("获取access_token返回：{}", JSON.toJSONString(token));
            SysRegUserExt userExt = null;
            if (!HlpUtils.isEmpty(userSid)) {
                // 绑定微信
                userExt = sysRegUserService.queryBySid(userSid);
                if (userExt == null) {
                    throw new HlpException("注册用户不存在");
                }
                if (!HlpUtils.isEmpty(userExt.getOpen_id())) {
                    throw new HlpException("该用户已绑定微信");
                }
                // 判断当前微信是否已绑定用户
                SysRegUserExt existUser = null;
                if (!HlpUtils.isEmpty(token.getUnionId())) {
                    existUser = sysRegUserService.queryValidByUnionId(token.getUnionId());
                }
                if (existUser == null && !HlpUtils.isEmpty(token.getOpenId())) {
                    existUser = sysRegUserService.queryValidByOpenId(token.getOpenId());
                }
                if (existUser != null) {
                    throw new HlpException("该微信已绑定用户：" + (!HlpUtils.isEmpty(existUser.getLogin_id()) ? existUser.getLogin_id() : existUser.getOpen_id()));
                }
                userExt.setOpen_id(token.getOpenId());
            } else {
                if (!HlpUtils.isEmpty(token.getUnionId())) {
                    userExt = sysRegUserService.queryValidByUnionId(token.getUnionId());
                }
                if (userExt == null && !HlpUtils.isEmpty(token.getOpenId())) {
                    userExt = sysRegUserService.queryValidByOpenId(token.getOpenId());
                }
                if (userExt == null) {
                    WxOAuth2UserInfo userInfo = auth2Service.getUserInfo(token, null);
                    LOGGER.info("获取用户个人信息返回：{}", JSON.toJSONString(userInfo));
                    userExt = this.convert(userInfo);
                } else {
                    if (!HlpUtils.isEmpty(token.getUnionId())) {
                        userExt.setUnion_id(token.getUnionId());
                    }
                    userExt.setLast_logined_date(new Date());
                    userExt.setLogin_count(userExt.getLogin_count() == null ? 1 : userExt.getLogin_count() + 1);
                }
            }
            if (HlpUtils.isEmpty(userExt.getAuth_src())) {
                userExt.setAuth_src(SysRegUserExt.AUTH_SRC_MP);
            }
            sysRegUserService.saveSysRegUser(userExt, null);

            userExt.setOpen_id(token.getOpenId());
            AccessToken accessToken = new AccessToken();
            SysUserExt sysUserExt = new SysUserExt(userExt);
            String jwtToken = JwtHelper.createJWT(JwtConstants.TYPE_REG_USER, sysUserExt);
            accessToken.setToken(jwtToken);
            accessToken.setExpires_in(JwtUtils.getTokenExpiredSeconds());
            userExt.setAccessToken(accessToken);
            hr.setData(userExt);
            hr.setCode(ErrorCode.CODE_SUCCESS);
        } catch (Exception e) {
            if (e instanceof WxErrorException) {
                hr.setCode(ErrorCode.CODE_PROC_ERROR);
                hr.setMessage(e.getMessage());
            } else {
                super.procException(hr, e);
            }
        }
        return hr;
    }

    @ApiOperation(value = "微信小程序登录")
    @ResponseBody
    @RequestMapping(value = "/loginByXcx.action", method = RequestMethod.POST)
    public HttpResult<Object> loginByXcx() {
        HttpResult<Object> hr = new HttpResult<>();
        try {
            super.setAccountSetFromParams();
            String dataSource = DataSourceContextHolder.getDataSourceType();
            Map<String, Object> map = super.getParams(request);
            String code = HlpUtils.getString(map, "code");
            WxMaJscode2SessionResult result = this.getSessionInfo(dataSource, code);
            if (HlpUtils.isEmpty(result.getOpenid()) || HlpUtils.isEmpty(result.getSessionKey())) {
                LOGGER.error("微信小程序登录,调用官方接口失败：{}", code);
                throw new HlpException("微信小程序登录,调用官方接口失败");
            }
            LOGGER.info("微信小程序登录,调用官方接口返回：{}", JSON.toJSONString(result));
            SysRegUserExt userExt = this.saveSysRegUser4WxMiniApp(result.getUnionid(), result.getOpenid());
            this.genRegUserToken(userExt);
            hr.setData(userExt);
            hr.setCode(ErrorCode.CODE_SUCCESS);
        } catch (Exception e) {
            if (e instanceof WxErrorException) {
                hr.setCode(ErrorCode.CODE_PROC_ERROR);
                hr.setMessage(e.getMessage());
            } else {
                super.procException(hr, e);
            }
        }
        return hr;
    }

    private SysRegUserExt saveSysRegUser4WxMiniApp(String unionId, String openId) throws Exception {
        SysRegUserExt userExt = sysRegUserService.queryValidByUnionIdOrOpenId(unionId, openId);
        if (userExt == null) {
            userExt = new SysRegUserExt();
            userExt.setOpen_id(openId);
            userExt.setUnion_id(unionId);
            userExt.setName("小程序用户");
            userExt.setUser_type(SysRegUserExt.USER_TYPE_PUTONG);
            userExt.setBind_date(new Date());
            userExt.setLast_logined_date(new Date());
            userExt.setLogin_count(1);
        } else {
            if (!HlpUtils.isEmpty(unionId)) {
                userExt.setUnion_id(unionId);
            }
            userExt.setLast_logined_date(new Date());
            userExt.setLogin_count(userExt.getLogin_count() == null ? 1 : userExt.getLogin_count() + 1);
        }
        if (HlpUtils.isEmpty(userExt.getAuth_src())) {
            userExt.setAuth_src(SysRegUserExt.AUTH_SRC_MINIAPP);
        }
        sysRegUserService.saveSysRegUser(userExt, null);
        return userExt;
    }

    /**
     * 生成注册用户的token
     *
     * @param userExt
     */
    private void genRegUserToken(SysRegUserExt userExt) throws HlpException {
        if (userExt != null) {
            AccessToken accessToken = new AccessToken();
            SysUserExt sysUserExt = new SysUserExt(userExt);
            String jwtToken = JwtHelper.createJWT(JwtConstants.TYPE_REG_USER, sysUserExt);
            accessToken.setToken(jwtToken);
            accessToken.setExpires_in(JwtUtils.getTokenExpiredSeconds());
            userExt.setAccessToken(accessToken);
        }
    }

    @ApiOperation(value = "模拟微信登录", notes = "模拟微信登录")
    @ResponseBody
    @RequestMapping(value = "/simulateWxLogin.action", method = RequestMethod.POST)
    public HttpResult<Object> simulateWxLogin() {
        HttpResult<Object> hr = new HttpResult<>();
        try {
            super.setAccountSetFromParams();
            Map<String, Object> map = super.getParams(request);
            String openId = HlpUtils.getString(map, "open_id");
            super.checkEmpty(openId, "请提供参数：open_id");
            SysRegUserExt userExt = sysRegUserService.queryValidByOpenId(openId);
            if (userExt != null) {
                SysUserExt opUser = new SysUserExt(userExt);
                String jwtToken = JwtHelper.createJWT(JwtConstants.TYPE_REG_USER, opUser);
                hr.setData(jwtToken);
            }
            hr.setCode(ErrorCode.CODE_SUCCESS);
        } catch (Exception e) {
            super.procException(hr, e);
        }
        return hr;
    }

    @ApiOperation(value = "微信小程序：根据code 到后台换取 openId, sessionKey, unionId,如果已经有绑定用户的同时返回登录信息")
    @ResponseBody
    @RequestMapping(value = "/getWxSessionInfoByJscode.action")
    public HttpResult<Object> getWxSessionInfoByJscode() {
        HttpResult<Object> hr = new HttpResult<>();
        try {
            super.setAccountSetFromParams();
            String dataSource = DataSourceContextHolder.getDataSourceType();
            Map<String, Object> map = super.getParams(request);
            String code = HlpUtils.getString(map, "code");
            WxMaJscode2SessionResult result = this.getSessionInfo(dataSource, code);
            SysRegUserExt userExt = this.saveSysRegUser4WxMiniApp(result.getUnionid(), result.getOpenid());
            userExt.setOpen_id(result.getOpenid());
            this.genRegUserToken(userExt);
            Map<String, Object> resultMap = new HashMap<>(2);
            resultMap.put("wx_session", result);
            resultMap.put("reg_user", userExt);
            hr.setData(resultMap);
            hr.setCode(ErrorCode.CODE_SUCCESS);
        } catch (Exception e) {
            if (e instanceof WxErrorException) {
                hr.setCode(ErrorCode.CODE_PROC_ERROR);
                hr.setMessage(e.getMessage());
            } else {
                super.procException(hr, e);
            }
        }
        return hr;
    }

    private WxMaJscode2SessionResult getSessionInfo(String dataSource, String code) throws Exception {
        MiniappSetting setting = wxSettingService.getWxMiniappSetting();
        // 1、初始化
        WX_MA_SERVICE.addConfig(dataSource, setting.initWxMiniappConfig());
        // 2、登录凭证校验
        WxMaUserService userService = WX_MA_SERVICE.getUserService();
        WxMaJscode2SessionResult result = userService.getSessionInfo(code);
        if (HlpUtils.isEmpty(result.getOpenid()) || HlpUtils.isEmpty(result.getSessionKey())) {
            LOGGER.error("微信小程序登录,调用官方接口失败：{}", code);
            throw new HlpException("微信小程序登录,调用官方接口失败");
        }
        LOGGER.info("微信小程序登录,调用官方接口返回：{}", JSON.toJSONString(result));
        return result;
    }

    @ApiOperation(value = "获取调用jsapi时所需要的签名")
    @ResponseBody
    @RequestMapping(value = "/getJsapiSignature.action", method = RequestMethod.POST)
    public HttpResult<Object> getJsapiSignature() {
        HttpResult<Object> hr = new HttpResult<>();
        try {
            Map<String, Object> map = super.getParams(request);
            String url = HlpUtils.getString(map, "url");
            super.checkEmpty(url, "缺少参数：url");
            String dataSource = DataSourceContextHolder.getDataSourceType();
            MpSetting setting = wxSettingService.getWxMpSetting();
            // 1、初始化
            WX_MP_SERVICE.addConfigStorage(dataSource, setting.initWxMpConfig());
            // 2、获取jsapi时所需要的签名
            WxJsapiSignature signature = WX_MP_SERVICE.createJsapiSignature(url);
            hr = HttpResult.success(signature);
        } catch (Exception e) {
            super.procException(hr, e);
        }
        return hr;
    }
}
